Project run parameters

no_tasks=2
seed = 9384
outcome = 'h5mn8'
fbase = '/scratch/p_gaydosh_lab/DSI/'
results_directory <- str_c(fbase, outcome, '/2020-10-20_results_454a961')
set.seed(seed)

Purpose. In this work, we will explore the relation between identified measures of despair of interest (e.g., personality measures of self-consciousness, individual and composite item scores from the CES-D assessment) and descriptors of diseases of despair. We will achieve this goal through modeling the outcomes based on the included predictors, and robustly assess the importance of the included features in predicting the outcomes via bootstrapping. We will use two well-known machine learning models, random forests and LASSO, which are both frequently used to measure the relative importance of the predictors included in the models. Lastly, we’ll generate trained and tuned models using this reduced feature set which can be used by others wish to predict the identified outcomes.

Subject inclusion. For this investigation, we will omit the entirety of Wave 2. This is commonly done in analyses of AddHealth data due the design of the original study. Otherwise, our dataset will include only subjects who have predictor and outcome data in all of the waves.

Outcome variables. In this experiment, we assess suicidal ideation at Wave 5.

Predictor variables. The predictors for these models are hand-picked, and based on previous work, relevance, and subject matter expertise. The set of predictors and the set of outcomes are disjoint. Predictors from Waves 1-4 (excluding Wave 2, see above) are included, and will be detailed in the following analysis.

Pipeline overview

Dataset generation

The predictors we will be using will be the the variable predictor_list loaded from 10-import-data.Rmd file. These initial set of predictors will be based of the list of variables that describe anxiety, depression, and optimism.

## set outcome variable of interest
filebase = '/scratch/p_gaydosh_lab'

#create data in specified form
dataset_list <- generate_datasets(outcome, binarize=FALSE, filebase=filebase, seed_val=seed)
Parsed with column specification:
cols(
  variable = col_character(),
  wave = col_double(),
  na_level = col_double(),
  type = col_character()
)
Note: Using an external vector in selections is ambiguous.
ℹ Use `all_of(var_name)` instead of `var_name` to silence this message.
ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
0 subjects removed from dataset.
Note: Using an external vector in selections is ambiguous.
ℹ Use `all_of(outcome)` instead of `outcome` to silence this message.
ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
[1] "Recoding Missing Factor Variables"
[1] "Number of factor variables being recoded :  98"
[1] "Factor variables being recoded : "
 [1] "h1fs1"   "h1fs3"   "h1fs4"   "h1fs5"   "h1fs6"   "h1fs7"   "h1fs11"  "h1fs15"  "h1fs16"  "h1fs17"  "h1fs19"  "h1ee14" 
[13] "h1ee12"  "h1ds14"  "h1ed9"   "h1ed7"   "h1ds3"   "h1fv7"   "h1fv1"   "h1fv8"   "h1jo9"   "h1ds13"  "h1ds12"  "h1ds11" 
[25] "h3sp5"   "h3sp6"   "h3sp7"   "h3sp8"   "h3sp9"   "h3sp10"  "h3sp11"  "h3sp12"  "h3sp13"  "h3ec56"  "h3ds18h" "h3ds18a"
[37] "h3ds18i" "h3to49"  "h3ds6"   "h3ds5"   "h3ds4"   "h4id5h"  "h4mh18"  "h4mh19"  "h4mh20"  "h4mh21"  "h4mh22"  "h4mh23" 
[49] "h4mh24"  "h4mh25"  "h4mh26"  "h4mh27"  "h4id5j"  "h4pe6"   "h4pe14"  "h4pe22"  "h4pe30"  "h4pe7"   "h4pe15"  "h4pe23" 
[61] "h4pe31"  "h4mh3"   "h4mh4"   "h4mh5"   "h4mh6"   "h4mh2"   "hdl"     "ldl"     "tg"      "h4bpcls" "h4ds7"   "h4ds19" 
[73] "h4ds14"  "h4ds20"  "h4ds6"   "h4ds5"   "h4ds4"   "h4cj17"  "h5id6g"  "h5ss0a"  "h5ss0b"  "h5ss0c"  "h5ss0d"  "h5ss0e" 
[85] "h5id6i"  "h5pe1"   "h5pe2"   "h5pe3"   "h5mn1"   "h5mn2"   "h5mn3"   "h5mn4"   "h5cj1d"  "h5cj1e"  "h5cj1f"  "h5cj1b" 
[97] "h5cj1c"  "h5cj1a" 
[1] "h1fs1"
[1] "h1fs3"
[1] "h1fs4"
[1] "h1fs5"
[1] "h1fs6"
[1] "h1fs7"
[1] "h1fs11"
[1] "h1fs15"
[1] "h1fs16"
[1] "h1fs17"
[1] "h1fs19"
[1] "h1ee14"
[1] "h1ee12"
[1] "h1ds14"
[1] "h1ed9"
[1] "h1ed7"
[1] "h1ds3"
[1] "h1fv7"
[1] "h1fv1"
[1] "h1fv8"
[1] "h1jo9"
[1] "h1ds13"
[1] "h1ds12"
[1] "h1ds11"
[1] "h3sp5"
[1] "h3sp6"
[1] "h3sp7"
[1] "h3sp8"
[1] "h3sp9"
[1] "h3sp10"
[1] "h3sp11"
[1] "h3sp12"
[1] "h3sp13"
[1] "h3ec56"
[1] "h3ds18h"
[1] "h3ds18a"
[1] "h3ds18i"
[1] "h3to49"
[1] "h3ds6"
[1] "h3ds5"
[1] "h3ds4"
[1] "h4id5h"
[1] "h4mh18"
[1] "h4mh19"
[1] "h4mh20"
[1] "h4mh21"
[1] "h4mh22"
[1] "h4mh23"
[1] "h4mh24"
[1] "h4mh25"
[1] "h4mh26"
[1] "h4mh27"
[1] "h4id5j"
[1] "h4pe6"
[1] "h4pe14"
[1] "h4pe22"
[1] "h4pe30"
[1] "h4pe7"
[1] "h4pe15"
[1] "h4pe23"
[1] "h4pe31"
[1] "h4mh3"
[1] "h4mh4"
[1] "h4mh5"
[1] "h4mh6"
[1] "h4mh2"
[1] "hdl"
[1] "ldl"
[1] "tg"
[1] "h4bpcls"
[1] "h4ds7"
[1] "h4ds19"
[1] "h4ds14"
[1] "h4ds20"
[1] "h4ds6"
[1] "h4ds5"
[1] "h4ds4"
[1] "h4cj17"
[1] "h5id6g"
[1] "h5ss0a"
[1] "h5ss0b"
[1] "h5ss0c"
[1] "h5ss0d"
[1] "h5ss0e"
[1] "h5id6i"
[1] "h5pe1"
[1] "h5pe2"
[1] "h5pe3"
[1] "h5mn1"
[1] "h5mn2"
[1] "h5mn3"
[1] "h5mn4"
[1] "h5cj1d"
[1] "h5cj1e"
[1] "h5cj1f"
[1] "h5cj1b"
[1] "h5cj1c"
[1] "h5cj1a"
   h1fs1    h1fs3    h1fs4    h1fs5    h1fs6    h1fs7   h1fs11   h1fs15   h1fs16   h1fs17   h1fs19   h1ee14   h1ee12   h1ds14 
"factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" 
   h1ed9    h1ed7    h1ds3    h1fv7    h1fv1    h1fv8    h1jo9   h1ds13   h1ds12   h1ds11    h3sp5    h3sp6    h3sp7    h3sp8 
"factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" 
   h3sp9   h3sp10   h3sp11   h3sp12   h3sp13   h3ec56  h3ds18h  h3ds18a  h3ds18i   h3to49    h3ds6    h3ds5    h3ds4   h4id5h 
"factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" 
  h4mh18   h4mh19   h4mh20   h4mh21   h4mh22   h4mh23   h4mh24   h4mh25   h4mh26   h4mh27   h4id5j    h4pe6   h4pe14   h4pe22 
"factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" 
  h4pe30    h4pe7   h4pe15   h4pe23   h4pe31    h4mh3    h4mh4    h4mh5    h4mh6    h4mh2      hdl      ldl       tg  h4bpcls 
"factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" 
   h4ds7   h4ds19   h4ds14   h4ds20    h4ds6    h4ds5    h4ds4   h4cj17   h5id6g   h5ss0a   h5ss0b   h5ss0c   h5ss0d   h5ss0e 
"factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" 
  h5id6i    h5pe1    h5pe2    h5pe3    h5mn1    h5mn2    h5mn3    h5mn4   h5cj1d   h5cj1e   h5cj1f   h5cj1b   h5cj1c   h5cj1a 
"factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" "factor" 
[1] "Recoding Missing Numeric Variables"
[1] "Number of numeric variables being recoded :  7"
[1] "Numeric variables being recoded : "
[1] "h1ed2"   "h4bmi"   "hba1c"   "crp"     "h4waist" "h4sbp"   "h4dbp"  
[1] "Recoding Missing Factor Variables"
[1] "Number of factor variables being recoded :  1"
[1] "Factor variables being recoded : "
[1] "h5mn8"
[1] "h5mn8"
   h5mn8 
"factor" 
Note: Using an external vector in selections is ambiguous.
ℹ Use `all_of(strat_var)` instead of `strat_var` to silence this message.
ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
#parse out dataset components
wave_data <- dataset_list$wave_data
full_dataset <- dataset_list$full_dataset
ds_raw <- dataset_list$ds_raw_outcome
ds <- dataset_list$ds_final

#ml splits of the data
training_df <- dataset_list$training_df
validation_df <- dataset_list$validation_df
testing_df <- dataset_list$testing_df

Outcome variable: Binarizing and recoding details

The following table details the values present in the outcome variable. As we can see, we need to convert the variable into a factor, and drop the NAs.

ds_raw %>% 
  group_by(.data[[outcome]]) %>% 
  summarise(total = n(), type = class(.data[[outcome]]))

ds %>% 
  group_by(.data[[outcome]]) %>% 
  summarise(total = n(), type = class(.data[[outcome]]))

print(str_c('Total number of subjects in unprocessed outcome data: ', nrow(ds_raw)))
[1] "Total number of subjects in unprocessed outcome data: 9349"
print(str_c('Total number of subjects in processed outcome data: ', nrow(ds)))
[1] "Total number of subjects in processed outcome data: 9168"
print(str_c('Total subjects dropped due to NA or skip: ', nrow(ds_raw)-nrow(ds)))
[1] "Total subjects dropped due to NA or skip: 181"

The table also demonstrates that the classes are very imbalanced, with about 14x the negative class as compared with the positive class. This is displayed graphically for easy consumption below.

ds_raw %>%
  explore_outcome(ds, outcome)
The original raw outcome data is displayed by converting it to a factor.  For more information about its actual type, investigate the ds_raw dataframe.  It is most likely a double.

After dropping the NAs, we see that we now have 9168 rows, which is consistent with the table above. The distribution of the data has a distinct imbalance as noted above. I think this warrants using pr_auc as the optimization and selection metric.

Data exploration and visualization

Here, we comment about the general characteristics of the data based on the provided visualizations. We comment on missingness of data, any strange or unusual behavior (e.g., strong imbalances), and any correlation that sticks out.

#Report about the characteristics of the subjects left out of the join
ds %>% explore_dropped()

# Visualize distributions of variables of interest
ds %>% 
  dplyr::select(-aid) %>%
  graph_bar_discrete(df = .,
                     plot_title = "Distributions of Discrete Variables",
                     max_categories = 50,
                     num_rows = 3,
                     num_cols = 3,
                     x_axis_size = 12,
                     y_axis_size = 12,
                     title_size = 15)

ds %>%
  graph_missing(only_missing = TRUE,
                title = "Percent Missing",
                box_line_size = .5,
                label_size = .5,
                x_axis_size = 12,
                y_axis_size = 12,
                title_size = 15)

ds %>%
  #dplyr::select(1:20) %>%
  pairwise_cramers_v() %>%
  plot_cramer_v(x_axis_angle = 90,
                plot_title = "Association among Categorical Variables",
                interactive = TRUE)

The correlation plot suggests that there are several variables we might think about removing. Firstly, there are moderate correlations among the individual predictor blocks; this is due to the way that they are ordered, since they’re essentially grouped into subsets. Addtionally, some predictor pairs have extremely high correlations, like (h4id5j, h4mh26) (correlation of 0.72), and many variables in the h4mh* series. (h3id5j, h4id5h) also has high correlation > 0.5. We may want to consider removing several of these in addition to the age variables because it may cause feature importance masking within our approaches.

Get good tasks

no_tasks <- get_good_task_ids(results_directory)

Robust feature evaluation

The following table displays the mean performance metrics for the bootstrapped models on the validation set, removing values for which there are NA.


mean_bs_rf_perf <- bs_rf_perf %>%
  summarise_if(is.numeric, mean, na.rm=TRUE) %>%
  mutate(model = 'bs_rf') %>%
  dplyr::select(model, everything())

mean_bs_rf_perf

As shown, the bootstrapped models tend to have high specificity but low sensitivity, indicating that there is a challenge in identifying subjects with suicidal ideation.

Feature importances: Random Forest

Mean decrease in impurity (MDI)

boot_rf_mdi <- list(bs_rf_mdi) %>%
  get_median_placement(use_base_var = TRUE) %>%
  add_attribute_names('predictor', full_dataset) %>%
  dplyr::select(predictor, att_name, overall_rank)

head(boot_rf_mdi, 20)

This table returns the MDI variable importance ranks that returned from each of the bootstrapped models.

# Needs to be fixed so that axes don't overlap each other and obscure understanding
plot_placement_boxplot(list(bs_rf_mdi))

Permutation importance

Now, let’s look at the permutation importance:

met <- 'pr_auc'
bs_rf_perm <- bs_rf_perm_plt %>%
  get_permute_placement(metric_oi=met) %>%
  add_attribute_names('predictor', full_dataset) %>%
  dplyr::select(predictor, everything())

head(bs_rf_perm, 20)

MDI vs Permutation importance

In this step, we assess the differences generated between the different types of importances.

cbind(boot_rf_mdi[1:20,], dplyr::select(bs_rf_perm[1:20,], -all_of(met)))

As shown, the MDI importance suffers from imbalances due to the number of values associated with a predictor. Because the wave ages have so many more values than the other factors, this artificially inflates their importance in MDI. The permutation importance is more intuitive.

plot_permute_var_imp(bs_rf_perm, metric = met)

LASSO model

In this step, we model the relation between the outcomes and the predictors using a linear regression with L2 regularization. This drives the importance of unimportant and redudant features towards zero.

mean_bs_lasso_perf <- bs_lasso_perf %>%
  summarise_if(is.numeric, mean, na.rm=TRUE) %>% 
  mutate(model='bs_lasso') %>%
  dplyr::select(model, everything())

mean_bs_lasso_perf

Feature importances: LASSO

Coefficient-based variable importance

boot_lasso_mdi <- list(bs_lasso_mdi) %>%
  get_median_placement(use_base_var = TRUE) %>%
  add_attribute_names('predictor', full_dataset) %>%
  dplyr::select(predictor, att_name, overall_rank)

head(boot_lasso_mdi, 20)
plot_placement_boxplot(list(bs_lasso_mdi))

Permutation importance

bs_lasso_perm <- bs_lasso_perm_plt %>%
  get_permute_placement(metric_oi=met) %>% #set in random forest section
  add_attribute_names('predictor', full_dataset) %>%
  dplyr::select(predictor, everything())

head(bs_lasso_perm, 20)
plot_permute_var_imp(bs_lasso_perm, metric = met)

Coefficient vs. Permutation importance

Now, we compare the feature importances generated by the two different approaches. The traditional method of evaluating feature importance for regression methods is through analysis of the coefficients.

cbind(boot_lasso_mdi[1:20,], dplyr::select(bs_lasso_perm[1:20,], -met))

Comparison: Model Type Mean Performance

The following table compares the mean performance of bootstrapped random forests to the mean performance of bootstrapped LASSO methods.

bs_comp_perfs <- rbind(mean_bs_rf_perf, mean_bs_lasso_perf) 
bs_comp_perfs

Comparison: Model Type Feature Importance

Here, we look at the aggregated results of the bootstrapped predictors and compare the models generated to each other.

joined_results <- bs_rf_perm %>%
  dplyr::select(-met) %>%
  full_join(dplyr::select(bs_lasso_perm, -met), by=c("predictor", "att_name"), suffix=c('.rf', '.lasso')) %>%
  mutate(mean_rank = (overall_rank.rf+overall_rank.lasso)/2) %>%
  arrange(mean_rank)

head(joined_results, 20)

The following visualization provides the intuition about the differences in the rankings between model types. They’re ordered by the overall mean importance, and for a given variable, the differences in rank are shown.

# Comparison of top_n features
joined_results %>%
  compare_feature_select(interactive = TRUE,
                         top_n = 100,
                         opacity = 0.50,
                         plot_title = "Permutation Importance of Predictors by Model")

Generation of final model

RF model

In this step, we build the final model for the random forest. We use slightly more values in order to come up with the best model, keeping in mind the number of combinations that are required to run to evaluate the grid.


final_rf_perf = NULL
Warning messages:
1: In grDevices::png(f) : unable to open connection to X11 display ''
2: In grDevices::png(f) : unable to open connection to X11 display ''
  
# read file array
final_rf_perfs = load_from_csv(final_rf_perf, results_directory, no_tasks)

# get the index of the best performance and best performance
final_rfmodel_ind <- which.max(dplyr::select(final_rf_perfs, met) %>% pull(met))
final_rf_perf <- final_rf_perfs %>% slice(final_rfmodel_ind)

# load the model of interest
final_model_rf <- load_best_model('final_rf_model', results_directory, no_tasks[[final_rfmodel_ind]])

Performance

The final random forest performance metrics are shown below:

# show model final performance
print(final_rf_perf)

Features: permutation importance


final_rf_perm_plt = NULL
  
# read best perm plt
final_rf_perm_plt = load_from_csv(final_rf_perm_plt, results_directory, best_ind=no_tasks[[final_rfmodel_ind]])
final_rf_perm <- final_rf_perm_plt %>%
  get_permute_placement(metric_oi=met) %>%
  add_attribute_names('predictor', full_dataset) %>%
  dplyr::select(predictor, everything())

head(final_rf_perm, 20)
plot_permute_var_imp(final_rf_perm, metric = met)

Comparison with bootstrap results

This section investigates the differences in the bootstrap results vs the features generated from the random forest final model. The following table shows the overall differences in rank.

rf_joined_results <- final_rf_perm %>%
  dplyr::select(-met) %>%
  full_join(dplyr::select(bs_rf_perm, -met), by=c("predictor", "att_name"), suffix=c('.final', '.bootstrap')) %>%
  mutate(mean_rank = (overall_rank.final + overall_rank.bootstrap)/2) %>%
  arrange(mean_rank)

head(rf_joined_results, 20)

The following plot provides visualizations for the difference in the final model rankings vs the bootstrap.

# Comparison of top_n features
rf_joined_results %>%
  compare_feature_select(sel_cols = c("overall_rank.final", "overall_rank.bootstrap"),
    interactive = TRUE,
    top_n = 100,
    opacity = 0.50,
    plot_title = "Permutation Importance of Predictors: Final vs. Bootstrap")

LASSO model

Now, we create the final model for LASSO. There is no substantial difference between this method and the bootstrap methods, other than the data upon which the model is being built.


final_lasso_perf = NULL
  
# read file array
final_lasso_perfs = load_from_csv(final_lasso_perf, results_directory, no_tasks)

# get the index of the best performance and best performance
final_lassomodel_ind <- which.max(dplyr::select(final_lasso_perfs, met) %>% pull(met))
final_lasso_perf <- final_lasso_perfs %>% slice(final_lassomodel_ind)

# load the model of interest
final_model_lasso <- load_best_model('final_lasso_model', results_directory, no_tasks[[final_lassomodel_ind]] )

The final LASSO performance metrics are shown below:

# show model final performance
print(final_lasso_perf)

Features: permutation importance


final_lasso_perm_plt = NULL

#load best index permutation from file  
final_lasso_perm_plt = load_from_csv(final_lasso_perm_plt, results_directory, best_ind=no_tasks[[final_lassomodel_ind]])
final_lasso_perm <- final_lasso_perm_plt %>%
  get_permute_placement(metric_oi=met) %>%
  add_attribute_names('predictor', full_dataset) %>%
  dplyr::select(predictor, everything())

head(final_lasso_perm, 20)
plot_permute_var_imp(final_lasso_perm, metric = met)

Comparison with bootstrap results

This section investigates the differences in the bootstrap results vs the features generated from the LASSO final model. The following table shows the overall differences in rank.

lasso_joined_results <- final_lasso_perm %>%
  dplyr::select(-met) %>%
  full_join(dplyr::select(bs_lasso_perm, -met), by=c("predictor", "att_name"), suffix=c('.final', '.bootstrap')) %>%
  mutate(mean_rank = (overall_rank.final + overall_rank.bootstrap)/2) %>%
  arrange(mean_rank)

head(lasso_joined_results, 20)

The following plot provides visualizations for the difference in the final model rankings vs the bootstrap.

# Comparison of top_n features
lasso_joined_results %>%
  compare_feature_select(sel_cols = c("overall_rank.final", "overall_rank.bootstrap"),
    interactive = TRUE,
    top_n = 100,
    opacity = 0.50,
    plot_title = "Permutation Importance of Predictors: Final vs. Bootstrap")

Comparison: Final model features

Here, we compare the features generated by the permutation importance between the two final models.

rf_lasso_final_joined_results <- final_rf_perm %>%
  dplyr::select(-met) %>%
  full_join(dplyr::select(final_lasso_perm, -met), by=c("predictor", "att_name"), suffix=c('.rf', '.lasso')) %>%
  mutate(mean_rank = (overall_rank.rf+overall_rank.lasso)/2) %>%
  arrange(mean_rank)

head(rf_lasso_final_joined_results, 20)

The following visualization provides the intuition about the differences in the rankings between the final model types. They’re ordered by the overall mean importance, and for a given variable, the differences in rank are shown.

# Comparison of top_n features
rf_lasso_final_joined_results %>%
  compare_feature_select(sel_cols = c("overall_rank.rf", "overall_rank.lasso"),
    interactive = TRUE,
    top_n = 100,
    opacity = 0.50,
    plot_title = "Permutation Importance of Predictors: Random Forest vs Lasso")

Comparison: Final model performance

With the final models generated, we’re now able to compare their performance metrics.

# Comparison of performance metrics
valid_perf <- get_metric_set_from_perfs(perf_list = list(final_rf_perf, final_lasso_perf)) %>%
  mutate(model = c('rf', 'lasso'))

testing_perf <- get_metric_set_from_models(testing_df, list(final_model_rf, final_model_lasso), out=outcome) %>%
  mutate(model = c('rf', 'lasso'))

Validation and selection. The following table shows the comparison between models in terms of the validation set. We can select our final model based on the best performing model according to the metric.

print(valid_perf)

Testing performance. The following shows the performance of both the models on the test set. Note that although we don’t use this test set to evaluate the final models, we can still see how our selected method would have performed.

print(testing_perf)

The following plots show a comparison between the performance of the models on the validation and test sets. Again, we don’t choose the model based on the test set, but curiosity dictates that we view this performance.

# Show plots side by side
metrics_of_interest = c('model', 'accuracy', 'bal_accuracy', 'mpce', 'sens', 'spec', 'ppv', 'npv', 'pr_auc', 'roc_auc')
valid_plt <- plot_metric_set(dplyr::select(valid_perf, all_of(metrics_of_interest)), plot_title = "Model comparison for validation set")
test_plt <- plot_metric_set(dplyr::select(testing_perf, all_of(metrics_of_interest)), plot_title = "Model comparison for testing set")
gridExtra::grid.arrange(gridExtra::arrangeGrob(valid_plt, test_plt, ncol=2, nrow=1))

LS0tCnRpdGxlOiAiODEtaW50ZXJwcmV0LXN1aWNpZGFsIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdGhlbWU6IGx1bWVuCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19kZXB0aDogNAogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFKQpgYGAKCmBgYHtyIHNvdXJjZSBmaWxlcywgaW5jbHVkZT1GQUxTRX0Kc291cmNlKCJmdW5jdGlvbl9pbXBvcnQuUiIpCmBgYAoKIyMgUHJvamVjdCBydW4gcGFyYW1ldGVycwpgYGB7ciBzZWVkcyBmb3IgcmVwcm9kdWNpYmlsaXR5fQpub190YXNrcz0yCnNlZWQgPSA5Mzg0Cm91dGNvbWUgPSAnaDVtbjgnCmZiYXNlID0gJy9zY3JhdGNoL3BfZ2F5ZG9zaF9sYWIvRFNJLycKcmVzdWx0c19kaXJlY3RvcnkgPC0gc3RyX2MoZmJhc2UsIG91dGNvbWUsICcvMjAyMC0xMC0yMF9yZXN1bHRzXzQ1NGE5NjEnKQpzZXQuc2VlZChzZWVkKQpgYGAKCioqUHVycG9zZS4qKiBJbiB0aGlzIHdvcmssIHdlIHdpbGwgZXhwbG9yZSB0aGUgcmVsYXRpb24gYmV0d2VlbiBpZGVudGlmaWVkIG1lYXN1cmVzIG9mIGRlc3BhaXIgb2YgaW50ZXJlc3QgKGUuZy4sIHBlcnNvbmFsaXR5IG1lYXN1cmVzIG9mIHNlbGYtY29uc2Npb3VzbmVzcywgaW5kaXZpZHVhbCBhbmQgY29tcG9zaXRlIGl0ZW0gc2NvcmVzIGZyb20gdGhlIENFUy1EIGFzc2Vzc21lbnQpIGFuZCBkZXNjcmlwdG9ycyBvZiBkaXNlYXNlcyBvZiBkZXNwYWlyLiAgV2Ugd2lsbCBhY2hpZXZlIHRoaXMgZ29hbCB0aHJvdWdoIG1vZGVsaW5nIHRoZSBvdXRjb21lcyBiYXNlZCBvbiB0aGUgaW5jbHVkZWQgcHJlZGljdG9ycywgYW5kIHJvYnVzdGx5IGFzc2VzcyB0aGUgaW1wb3J0YW5jZSBvZiB0aGUgaW5jbHVkZWQgZmVhdHVyZXMgaW4gcHJlZGljdGluZyB0aGUgb3V0Y29tZXMgdmlhIGJvb3RzdHJhcHBpbmcuICBXZSB3aWxsIHVzZSB0d28gd2VsbC1rbm93biBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscywgcmFuZG9tIGZvcmVzdHMgYW5kIExBU1NPLCB3aGljaCBhcmUgYm90aCBmcmVxdWVudGx5IHVzZWQgdG8gbWVhc3VyZSB0aGUgcmVsYXRpdmUgaW1wb3J0YW5jZSBvZiB0aGUgcHJlZGljdG9ycyBpbmNsdWRlZCBpbiB0aGUgbW9kZWxzLiAgTGFzdGx5LCB3ZSdsbCBnZW5lcmF0ZSB0cmFpbmVkIGFuZCB0dW5lZCBtb2RlbHMgdXNpbmcgdGhpcyByZWR1Y2VkIGZlYXR1cmUgc2V0IHdoaWNoIGNhbiBiZSB1c2VkIGJ5IG90aGVycyB3aXNoIHRvIHByZWRpY3QgdGhlIGlkZW50aWZpZWQgb3V0Y29tZXMuCgoqKlN1YmplY3QgaW5jbHVzaW9uLioqIEZvciB0aGlzIGludmVzdGlnYXRpb24sIHdlIHdpbGwgb21pdCB0aGUgZW50aXJldHkgb2YgV2F2ZSAyLiAgVGhpcyBpcyBjb21tb25seSBkb25lIGluIGFuYWx5c2VzIG9mIEFkZEhlYWx0aCBkYXRhIGR1ZSB0aGUgZGVzaWduIG9mIHRoZSBvcmlnaW5hbCBzdHVkeS4gIE90aGVyd2lzZSwgb3VyIGRhdGFzZXQgd2lsbCBpbmNsdWRlIG9ubHkgc3ViamVjdHMgd2hvIGhhdmUgcHJlZGljdG9yIGFuZCBvdXRjb21lIGRhdGEgaW4gX2FsbF8gb2YgdGhlIHdhdmVzLgoKKipPdXRjb21lIHZhcmlhYmxlcy4qKiBJbiB0aGlzIGV4cGVyaW1lbnQsIHdlIGFzc2VzcyBfc3VpY2lkYWwgaWRlYXRpb25fIGF0IFdhdmUgNS4gIAoKKipQcmVkaWN0b3IgdmFyaWFibGVzLioqIFRoZSBwcmVkaWN0b3JzIGZvciB0aGVzZSBtb2RlbHMgYXJlIGhhbmQtcGlja2VkLCBhbmQgYmFzZWQgb24gcHJldmlvdXMgd29yaywgcmVsZXZhbmNlLCBhbmQgc3ViamVjdCBtYXR0ZXIgZXhwZXJ0aXNlLiBUaGUgc2V0IG9mIHByZWRpY3RvcnMgYW5kIHRoZSBzZXQgb2Ygb3V0Y29tZXMgYXJlIGRpc2pvaW50LiAgUHJlZGljdG9ycyBmcm9tIFdhdmVzIDEtNCAoZXhjbHVkaW5nIFdhdmUgMiwgc2VlIGFib3ZlKSBhcmUgaW5jbHVkZWQsIGFuZCB3aWxsIGJlIGRldGFpbGVkIGluIHRoZSBmb2xsb3dpbmcgYW5hbHlzaXMuCgpgYGB7ciBsb2FkIGxpYnJhcmllcywgaW5jbHVkZT1GQUxTRX0KIyBVc2UgcGFjbWFuLCB3aGljaCBmb3JjZXMgYW4gaW5zdGFsbCBpZiB0aGUgbGlicmFyeSBpc24ndCBwcmVzZW50IG9uIHRoZSBydW5uaW5nIG1hY2hpbmUKaWYgKCFyZXF1aXJlKCJwYWNtYW4iKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikKI3BhY21hbjo6cF9pbnN0YWxsKHBsb3RseSkKcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlLCBoMm8sIGZ1cnJyKQoKYGBgCgpgYGB7ciBpbml0aWFsaXphdGlvbnMsIGluY2x1ZGU9RkFMU0V9Cmgyby5pbml0KCkgCmgyby5ub19wcm9ncmVzcygpCmZ1dHVyZTo6cGxhbihtdWx0aXByb2Nlc3MpCmBgYAoKIyBQaXBlbGluZSBvdmVydmlldwpgYGB7ciBlY2hvPUZBTFNFLCBvdXQud2lkdGg9JzUwJSd9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCcuL2ltZy9waXBlbGluZV9vdmVydmlldy5qcGcnKQpgYGAKCiMgRGF0YXNldCBnZW5lcmF0aW9uCgpUaGUgcHJlZGljdG9ycyB3ZSB3aWxsIGJlIHVzaW5nIHdpbGwgYmUgdGhlIHRoZSB2YXJpYWJsZSBgcHJlZGljdG9yX2xpc3RgIGxvYWRlZCBmcm9tIGAxMC1pbXBvcnQtZGF0YS5SbWRgIGZpbGUuIFRoZXNlIGluaXRpYWwgc2V0IG9mIHByZWRpY3RvcnMgd2lsbCBiZSBiYXNlZCBvZiB0aGUgbGlzdCBvZiB2YXJpYWJsZXMgdGhhdCBkZXNjcmliZSBhbnhpZXR5LCBkZXByZXNzaW9uLCBhbmQgb3B0aW1pc20uCgpgYGB7ciBsb2FkIHJhdyBkYXRhIGFuZCBmb3JtdWxhdGUgZGF0YXNldCwgd2FybmluZz1UUlVFLCBtZXNzYWdlPVRSVUV9CiMjIHNldCBvdXRjb21lIHZhcmlhYmxlIG9mIGludGVyZXN0CmZpbGViYXNlID0gJy9zY3JhdGNoL3BfZ2F5ZG9zaF9sYWInCgojY3JlYXRlIGRhdGEgaW4gc3BlY2lmaWVkIGZvcm0KZGF0YXNldF9saXN0IDwtIGdlbmVyYXRlX2RhdGFzZXRzKG91dGNvbWUsIGJpbmFyaXplPUZBTFNFLCBmaWxlYmFzZT1maWxlYmFzZSwgc2VlZF92YWw9c2VlZCkKCiNwYXJzZSBvdXQgZGF0YXNldCBjb21wb25lbnRzCndhdmVfZGF0YSA8LSBkYXRhc2V0X2xpc3Qkd2F2ZV9kYXRhCmZ1bGxfZGF0YXNldCA8LSBkYXRhc2V0X2xpc3QkZnVsbF9kYXRhc2V0CmRzX3JhdyA8LSBkYXRhc2V0X2xpc3QkZHNfcmF3X291dGNvbWUKZHMgPC0gZGF0YXNldF9saXN0JGRzX2ZpbmFsCgojbWwgc3BsaXRzIG9mIHRoZSBkYXRhCnRyYWluaW5nX2RmIDwtIGRhdGFzZXRfbGlzdCR0cmFpbmluZ19kZgp2YWxpZGF0aW9uX2RmIDwtIGRhdGFzZXRfbGlzdCR2YWxpZGF0aW9uX2RmCnRlc3RpbmdfZGYgPC0gZGF0YXNldF9saXN0JHRlc3RpbmdfZGYKCmBgYAoKIyMgT3V0Y29tZSB2YXJpYWJsZTogQmluYXJpemluZyBhbmQgcmVjb2RpbmcgZGV0YWlscwoKVGhlIGZvbGxvd2luZyB0YWJsZSBkZXRhaWxzIHRoZSB2YWx1ZXMgcHJlc2VudCBpbiB0aGUgb3V0Y29tZSB2YXJpYWJsZS4gIEFzIHdlIGNhbiBzZWUsIHdlIG5lZWQgdG8gY29udmVydCB0aGUgdmFyaWFibGUgaW50byBhIGZhY3RvciwgYW5kIGRyb3AgdGhlIE5Bcy4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmRzX3JhdyAlPiUgCiAgZ3JvdXBfYnkoLmRhdGFbW291dGNvbWVdXSkgJT4lIAogIHN1bW1hcmlzZSh0b3RhbCA9IG4oKSwgdHlwZSA9IGNsYXNzKC5kYXRhW1tvdXRjb21lXV0pKQoKZHMgJT4lIAogIGdyb3VwX2J5KC5kYXRhW1tvdXRjb21lXV0pICU+JSAKICBzdW1tYXJpc2UodG90YWwgPSBuKCksIHR5cGUgPSBjbGFzcyguZGF0YVtbb3V0Y29tZV1dKSkKCnByaW50KHN0cl9jKCdUb3RhbCBudW1iZXIgb2Ygc3ViamVjdHMgaW4gdW5wcm9jZXNzZWQgb3V0Y29tZSBkYXRhOiAnLCBucm93KGRzX3JhdykpKQpwcmludChzdHJfYygnVG90YWwgbnVtYmVyIG9mIHN1YmplY3RzIGluIHByb2Nlc3NlZCBvdXRjb21lIGRhdGE6ICcsIG5yb3coZHMpKSkKcHJpbnQoc3RyX2MoJ1RvdGFsIHN1YmplY3RzIGRyb3BwZWQgZHVlIHRvIE5BIG9yIHNraXA6ICcsIG5yb3coZHNfcmF3KS1ucm93KGRzKSkpCgpgYGAKClRoZSB0YWJsZSBhbHNvIGRlbW9uc3RyYXRlcyB0aGF0IHRoZSBjbGFzc2VzIGFyZSB2ZXJ5IGltYmFsYW5jZWQsIHdpdGggYWJvdXQgMTR4IHRoZSBuZWdhdGl2ZSBjbGFzcyBhcyBjb21wYXJlZCB3aXRoIHRoZSBwb3NpdGl2ZSBjbGFzcy4gIFRoaXMgaXMgZGlzcGxheWVkIGdyYXBoaWNhbGx5IGZvciBlYXN5IGNvbnN1bXB0aW9uIGJlbG93LgoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9VFJVRX0KZHNfcmF3ICU+JQogIGV4cGxvcmVfb3V0Y29tZShkcywgb3V0Y29tZSkKCmBgYAoKQWZ0ZXIgZHJvcHBpbmcgdGhlIE5Bcywgd2Ugc2VlIHRoYXQgd2Ugbm93IGhhdmUgOTE2OCByb3dzLCB3aGljaCBpcyBjb25zaXN0ZW50IHdpdGggdGhlIHRhYmxlIGFib3ZlLiAgVGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZGF0YSBoYXMgYSBkaXN0aW5jdCBpbWJhbGFuY2UgYXMgbm90ZWQgYWJvdmUuICBJIHRoaW5rIHRoaXMgd2FycmFudHMgdXNpbmcgYHByX2F1Y2AgYXMgdGhlIG9wdGltaXphdGlvbiBhbmQgc2VsZWN0aW9uIG1ldHJpYy4KCiMgRGF0YSBleHBsb3JhdGlvbiBhbmQgdmlzdWFsaXphdGlvbgpIZXJlLCB3ZSBjb21tZW50IGFib3V0IHRoZSBnZW5lcmFsIGNoYXJhY3RlcmlzdGljcyBvZiB0aGUgZGF0YSBiYXNlZCBvbiB0aGUgcHJvdmlkZWQgdmlzdWFsaXphdGlvbnMuICBXZSBjb21tZW50IG9uIG1pc3NpbmduZXNzIG9mIGRhdGEsIGFueSBzdHJhbmdlIG9yIHVudXN1YWwgYmVoYXZpb3IgKGUuZy4sIHN0cm9uZyBpbWJhbGFuY2VzKSwgYW5kIGFueSBjb3JyZWxhdGlvbiB0aGF0IHN0aWNrcyBvdXQuCgpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojUmVwb3J0IGFib3V0IHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgdGhlIHN1YmplY3RzIGxlZnQgb3V0IG9mIHRoZSBqb2luCmRzICU+JSBleHBsb3JlX2Ryb3BwZWQoKQoKYGBgCgpgYGB7ciBlZGEgZGlzdHJpYnV0aW9ucywgZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQ9MTV9CiMgVmlzdWFsaXplIGRpc3RyaWJ1dGlvbnMgb2YgdmFyaWFibGVzIG9mIGludGVyZXN0CmRzICU+JSAKICBkcGx5cjo6c2VsZWN0KC1haWQpICU+JQogIGdyYXBoX2Jhcl9kaXNjcmV0ZShkZiA9IC4sCiAgICAgICAgICAgICAgICAgICAgIHBsb3RfdGl0bGUgPSAiRGlzdHJpYnV0aW9ucyBvZiBEaXNjcmV0ZSBWYXJpYWJsZXMiLAogICAgICAgICAgICAgICAgICAgICBtYXhfY2F0ZWdvcmllcyA9IDUwLAogICAgICAgICAgICAgICAgICAgICBudW1fcm93cyA9IDMsCiAgICAgICAgICAgICAgICAgICAgIG51bV9jb2xzID0gMywKICAgICAgICAgICAgICAgICAgICAgeF9heGlzX3NpemUgPSAxMiwKICAgICAgICAgICAgICAgICAgICAgeV9heGlzX3NpemUgPSAxMiwKICAgICAgICAgICAgICAgICAgICAgdGl0bGVfc2l6ZSA9IDE1KQpgYGAKCmBgYHtyIGVkYSBtaXNzaW5nfQpkcyAlPiUKICBncmFwaF9taXNzaW5nKG9ubHlfbWlzc2luZyA9IFRSVUUsCiAgICAgICAgICAgICAgICB0aXRsZSA9ICJQZXJjZW50IE1pc3NpbmciLAogICAgICAgICAgICAgICAgYm94X2xpbmVfc2l6ZSA9IC41LAogICAgICAgICAgICAgICAgbGFiZWxfc2l6ZSA9IC41LAogICAgICAgICAgICAgICAgeF9heGlzX3NpemUgPSAxMiwKICAgICAgICAgICAgICAgIHlfYXhpc19zaXplID0gMTIsCiAgICAgICAgICAgICAgICB0aXRsZV9zaXplID0gMTUpCgpgYGAKCmBgYHtyIGVkYSBjb3JyZWxhdGlvbnN9CmRzICU+JQogICNkcGx5cjo6c2VsZWN0KDE6MjApICU+JQogIHBhaXJ3aXNlX2NyYW1lcnNfdigpICU+JQogIHBsb3RfY3JhbWVyX3YoeF9heGlzX2FuZ2xlID0gOTAsCiAgICAgICAgICAgICAgICBwbG90X3RpdGxlID0gIkFzc29jaWF0aW9uIGFtb25nIENhdGVnb3JpY2FsIFZhcmlhYmxlcyIsCiAgICAgICAgICAgICAgICBpbnRlcmFjdGl2ZSA9IFRSVUUpCmBgYApUaGUgY29ycmVsYXRpb24gcGxvdCBzdWdnZXN0cyB0aGF0IHRoZXJlIGFyZSBzZXZlcmFsIHZhcmlhYmxlcyB3ZSBtaWdodCB0aGluayBhYm91dCByZW1vdmluZy4gIEZpcnN0bHksIHRoZXJlIGFyZSBtb2RlcmF0ZSBjb3JyZWxhdGlvbnMgYW1vbmcgdGhlIGluZGl2aWR1YWwgcHJlZGljdG9yIGJsb2NrczsgdGhpcyBpcyBkdWUgdG8gdGhlIHdheSB0aGF0IHRoZXkgYXJlIG9yZGVyZWQsIHNpbmNlIHRoZXkncmUgZXNzZW50aWFsbHkgZ3JvdXBlZCBpbnRvIHN1YnNldHMuICBBZGR0aW9uYWxseSwgc29tZSBwcmVkaWN0b3IgcGFpcnMgaGF2ZSBleHRyZW1lbHkgaGlnaCBjb3JyZWxhdGlvbnMsIGxpa2UgKGg0aWQ1aiwgaDRtaDI2KSAoY29ycmVsYXRpb24gb2YgMC43MiksIGFuZCBtYW55IHZhcmlhYmxlcyBpbiB0aGUgaDRtaCogc2VyaWVzLiAgKGgzaWQ1aiwgaDRpZDVoKSBhbHNvIGhhcyBoaWdoIGNvcnJlbGF0aW9uID4gMC41LiAgV2UgbWF5IHdhbnQgdG8gY29uc2lkZXIgcmVtb3Zpbmcgc2V2ZXJhbCBvZiB0aGVzZSBpbiBhZGRpdGlvbiB0byB0aGUgYWdlIHZhcmlhYmxlcyBiZWNhdXNlIGl0IG1heSBjYXVzZSBmZWF0dXJlIGltcG9ydGFuY2UgbWFza2luZyB3aXRoaW4gb3VyIGFwcHJvYWNoZXMuCgojIEdldCBnb29kIHRhc2tzCmBgYHtyfQpub190YXNrcyA8LSBnZXRfZ29vZF90YXNrX2lkcyhyZXN1bHRzX2RpcmVjdG9yeSkKYGBgCgoKIyBSb2J1c3QgZmVhdHVyZSBldmFsdWF0aW9uIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQoKVGhlIGZvbGxvd2luZyB0YWJsZSBkaXNwbGF5cyB0aGUgbWVhbiBwZXJmb3JtYW5jZSBtZXRyaWNzIGZvciB0aGUgYm9vdHN0cmFwcGVkIG1vZGVscyBvbiB0aGUgdmFsaWRhdGlvbiBzZXQsIHJlbW92aW5nIHZhbHVlcyBmb3Igd2hpY2ggdGhlcmUgYXJlIE5BLgoKYGBge3Igc2F2ZS9sb2FkIHJmIGJvb3RzdHJhcCBtb2RlbCBwZXJmb3JtYW5jZSwgaW5jbHVkZT1GQUxTRX0KCmJzX3JmX3BlcmYgPSBOVUxMCgojIGdldCBkYXRhIGZpbGVzCmJzX3JmX3BlcmYgPSBsb2FkX2Zyb21fY3N2KGJzX3JmX3BlcmYsIHJlc3VsdHNfZGlyZWN0b3J5LCBub190YXNrcykKCmBgYAoKCmBgYHtyIGV2YWx1YXRlIGJvb3RzdHJhcCBtb2RlbCBwZXJmb3JtYW5jZSByZiwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCm1lYW5fYnNfcmZfcGVyZiA8LSBic19yZl9wZXJmICU+JQogIHN1bW1hcmlzZV9pZihpcy5udW1lcmljLCBtZWFuLCBuYS5ybT1UUlVFKSAlPiUKICBtdXRhdGUobW9kZWwgPSAnYnNfcmYnKSAlPiUKICBkcGx5cjo6c2VsZWN0KG1vZGVsLCBldmVyeXRoaW5nKCkpCgptZWFuX2JzX3JmX3BlcmYKYGBgCkFzIHNob3duLCB0aGUgYm9vdHN0cmFwcGVkIG1vZGVscyB0ZW5kIHRvIGhhdmUgaGlnaCBzcGVjaWZpY2l0eSBidXQgbG93IHNlbnNpdGl2aXR5LCBpbmRpY2F0aW5nIHRoYXQgdGhlcmUgaXMgYSBjaGFsbGVuZ2UgaW4gaWRlbnRpZnlpbmcgc3ViamVjdHMgd2l0aCBzdWljaWRhbCBpZGVhdGlvbi4KCiMjIyBGZWF0dXJlIGltcG9ydGFuY2VzOiBSYW5kb20gRm9yZXN0CiMjIyMgTWVhbiBkZWNyZWFzZSBpbiBpbXB1cml0eSAoTURJKQoKYGBge3Igc2F2ZS9sb2FkIHJmIGJvb3RzdHJhcCBtb2RlbCBtZGksIGluY2x1ZGU9RkFMU0V9Cgpic19yZl9tZGkgPSBOVUxMCiAgCiMgZ2V0IGRhdGEgZmlsZXMKYnNfcmZfbWRpID0gbG9hZF9mcm9tX2Nzdihic19yZl9tZGksIHJlc3VsdHNfZGlyZWN0b3J5LCBub190YXNrcykKCgpgYGAKCmBgYHtyIGV2YWx1YXRlIGJvb3RzdHJhcCBtZGl9CmJvb3RfcmZfbWRpIDwtIGxpc3QoYnNfcmZfbWRpKSAlPiUKICBnZXRfbWVkaWFuX3BsYWNlbWVudCh1c2VfYmFzZV92YXIgPSBUUlVFKSAlPiUKICBhZGRfYXR0cmlidXRlX25hbWVzKCdwcmVkaWN0b3InLCBmdWxsX2RhdGFzZXQpICU+JQogIGRwbHlyOjpzZWxlY3QocHJlZGljdG9yLCBhdHRfbmFtZSwgb3ZlcmFsbF9yYW5rKQoKaGVhZChib290X3JmX21kaSwgMjApCmBgYApUaGlzIHRhYmxlIHJldHVybnMgdGhlIE1ESSB2YXJpYWJsZSBpbXBvcnRhbmNlIHJhbmtzIHRoYXQgcmV0dXJuZWQgZnJvbSBlYWNoIG9mIHRoZSBib290c3RyYXBwZWQgbW9kZWxzLgoKYGBge3IgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMn0KIyBOZWVkcyB0byBiZSBmaXhlZCBzbyB0aGF0IGF4ZXMgZG9uJ3Qgb3ZlcmxhcCBlYWNoIG90aGVyIGFuZCBvYnNjdXJlIHVuZGVyc3RhbmRpbmcKcGxvdF9wbGFjZW1lbnRfYm94cGxvdChsaXN0KGJzX3JmX21kaSkpCmBgYAoKIyMjIyBQZXJtdXRhdGlvbiBpbXBvcnRhbmNlCk5vdywgbGV0J3MgbG9vayBhdCB0aGUgcGVybXV0YXRpb24gaW1wb3J0YW5jZToKCmBgYHtyIGNvbXB1dGUvc2F2ZSBvciBsb2FkIGJzIHJmIG1vZGVsIHBlcm11dGF0aW9uLCBpbmNsdWRlPUZBTFNFfQoKYnNfcmZfcGVybV9wbHQgPC0gTlVMTAogIAojIGdldCBkYXRhIGZpbGVzCmJzX3JmX3Blcm1fcGx0IDwtIGxvYWRfZnJvbV9jc3YoYnNfcmZfcGVybV9wbHQsIHJlc3VsdHNfZGlyZWN0b3J5LCBub190YXNrcykKCmBgYAoKCmBgYHtyIGFnZ3JlZ2F0ZSByZiBwZXJtIHJlc3VsdHN9Cm1ldCA8LSAncHJfYXVjJwpic19yZl9wZXJtIDwtIGJzX3JmX3Blcm1fcGx0ICU+JQogIGdldF9wZXJtdXRlX3BsYWNlbWVudChtZXRyaWNfb2k9bWV0KSAlPiUKICBhZGRfYXR0cmlidXRlX25hbWVzKCdwcmVkaWN0b3InLCBmdWxsX2RhdGFzZXQpICU+JQogIGRwbHlyOjpzZWxlY3QocHJlZGljdG9yLCBldmVyeXRoaW5nKCkpCgpoZWFkKGJzX3JmX3Blcm0sIDIwKQpgYGAKCiMjIyMgTURJIHZzIFBlcm11dGF0aW9uIGltcG9ydGFuY2UKSW4gdGhpcyBzdGVwLCB3ZSBhc3Nlc3MgdGhlIGRpZmZlcmVuY2VzIGdlbmVyYXRlZCBiZXR3ZWVuIHRoZSBkaWZmZXJlbnQgdHlwZXMgb2YgaW1wb3J0YW5jZXMuCmBgYHtyIGZpZy53aWR0aCA9IDE2LCBmaWcuaGVpZ2h0ID0gMTR9CmNiaW5kKGJvb3RfcmZfbWRpWzE6MjAsXSwgZHBseXI6OnNlbGVjdChic19yZl9wZXJtWzE6MjAsXSwgLWFsbF9vZihtZXQpKSkKYGBgCkFzIHNob3duLCB0aGUgTURJIGltcG9ydGFuY2Ugc3VmZmVycyBmcm9tIGltYmFsYW5jZXMgZHVlIHRvIHRoZSBudW1iZXIgb2YgdmFsdWVzIGFzc29jaWF0ZWQgd2l0aCBhIHByZWRpY3Rvci4gIEJlY2F1c2UgdGhlIHdhdmUgYWdlcyBoYXZlIHNvIG1hbnkgbW9yZSB2YWx1ZXMgdGhhbiB0aGUgb3RoZXIgZmFjdG9ycywgdGhpcyBhcnRpZmljaWFsbHkgaW5mbGF0ZXMgdGhlaXIgaW1wb3J0YW5jZSBpbiBNREkuICBUaGUgcGVybXV0YXRpb24gaW1wb3J0YW5jZSBpcyBtb3JlIGludHVpdGl2ZS4KCmBgYHtyIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTJ9CnBsb3RfcGVybXV0ZV92YXJfaW1wKGJzX3JmX3Blcm0sIG1ldHJpYyA9IG1ldCkKYGBgCgoKIyMgTEFTU08gbW9kZWwKSW4gdGhpcyBzdGVwLCB3ZSBtb2RlbCB0aGUgcmVsYXRpb24gYmV0d2VlbiB0aGUgb3V0Y29tZXMgYW5kIHRoZSBwcmVkaWN0b3JzIHVzaW5nIGEgbGluZWFyIHJlZ3Jlc3Npb24gd2l0aCBMMiByZWd1bGFyaXphdGlvbi4gIFRoaXMgZHJpdmVzIHRoZSBpbXBvcnRhbmNlIG9mIHVuaW1wb3J0YW50IGFuZCByZWR1ZGFudCBmZWF0dXJlcyB0b3dhcmRzIHplcm8uCgoKYGBge3Igc2F2ZS9sb2FkIGxhc3NvIGJvb3RzdHJhcCBtb2RlbCBwZXJmb3JtYW5jZSwgaW5jbHVkZT1GQUxTRX0KCmJzX2xhc3NvX3BlcmYgPSBOVUxMCiAgCiMgZ2V0IGRhdGEgZmlsZXMKYnNfbGFzc29fcGVyZiA9IGxvYWRfZnJvbV9jc3YoYnNfbGFzc29fcGVyZiwgcmVzdWx0c19kaXJlY3RvcnksIG5vX3Rhc2tzKQoKCmBgYAoKYGBge3IgZXZhbHVhdGUgYm9vdHN0cmFwIG1vZGVsIHBlcmZvcm1hbmNlIGxhc3NvLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQptZWFuX2JzX2xhc3NvX3BlcmYgPC0gYnNfbGFzc29fcGVyZiAlPiUKICBzdW1tYXJpc2VfaWYoaXMubnVtZXJpYywgbWVhbiwgbmEucm09VFJVRSkgJT4lIAogIG11dGF0ZShtb2RlbD0nYnNfbGFzc28nKSAlPiUKICBkcGx5cjo6c2VsZWN0KG1vZGVsLCBldmVyeXRoaW5nKCkpCgptZWFuX2JzX2xhc3NvX3BlcmYKYGBgCgojIyMgRmVhdHVyZSBpbXBvcnRhbmNlczogTEFTU08KIyMjIyBDb2VmZmljaWVudC1iYXNlZCB2YXJpYWJsZSBpbXBvcnRhbmNlCgpgYGB7ciBzYXZlL2xvYWQgbGFzc28gYm9vdHN0cmFwIG1vZGVsIG1kaSwgaW5jbHVkZT1GQUxTRX0KCmJzX2xhc3NvX21kaSA9IE5VTEwKICAKIyBnZXQgZGF0YSBmaWxlcwpic19sYXNzb19tZGkgPSBsb2FkX2Zyb21fY3N2KGJzX2xhc3NvX21kaSwgcmVzdWx0c19kaXJlY3RvcnksIG5vX3Rhc2tzKQoKYGBgCgpgYGB7ciBzaG93IGJzIGxhc3NvIG5vcm1hbGl6ZWQgY29lZmZpY2llbnRzfQpib290X2xhc3NvX21kaSA8LSBsaXN0KGJzX2xhc3NvX21kaSkgJT4lCiAgZ2V0X21lZGlhbl9wbGFjZW1lbnQodXNlX2Jhc2VfdmFyID0gVFJVRSkgJT4lCiAgYWRkX2F0dHJpYnV0ZV9uYW1lcygncHJlZGljdG9yJywgZnVsbF9kYXRhc2V0KSAlPiUKICBkcGx5cjo6c2VsZWN0KHByZWRpY3RvciwgYXR0X25hbWUsIG92ZXJhbGxfcmFuaykKCmhlYWQoYm9vdF9sYXNzb19tZGksIDIwKQpgYGAKCmBgYHtyIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTJ9CnBsb3RfcGxhY2VtZW50X2JveHBsb3QobGlzdChic19sYXNzb19tZGkpKQpgYGAKCiMjIyMgUGVybXV0YXRpb24gaW1wb3J0YW5jZQoKYGBge3IgY29tcHV0ZS9zYXZlIG9yIGxvYWQgYnMgbGFzc28gbW9kZWwgcGVybXV0YXRpb24sIGluY2x1ZGU9RkFMU0V9Cgpic19sYXNzb19wZXJtX3BsdCA9IE5VTEwKICAKIyBnZXQgZGF0YSBmaWxlcwpic19sYXNzb19wZXJtX3BsdCA9IGxvYWRfZnJvbV9jc3YoYnNfbGFzc29fcGVybV9wbHQsIHJlc3VsdHNfZGlyZWN0b3J5LCBub190YXNrcykKCmBgYAoKCmBgYHtyIGFnZ3JlZ2F0ZSBsYXNzbyBwZXJtdXRhdGlvbnMgYW5kIGdldCBtZXRyaWNzfQpic19sYXNzb19wZXJtIDwtIGJzX2xhc3NvX3Blcm1fcGx0ICU+JQogIGdldF9wZXJtdXRlX3BsYWNlbWVudChtZXRyaWNfb2k9bWV0KSAlPiUgI3NldCBpbiByYW5kb20gZm9yZXN0IHNlY3Rpb24KICBhZGRfYXR0cmlidXRlX25hbWVzKCdwcmVkaWN0b3InLCBmdWxsX2RhdGFzZXQpICU+JQogIGRwbHlyOjpzZWxlY3QocHJlZGljdG9yLCBldmVyeXRoaW5nKCkpCgpoZWFkKGJzX2xhc3NvX3Blcm0sIDIwKQpgYGAKCmBgYHtyIHBsb3QgbGFzc28gcGVybXV0YXRpb24sIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTJ9CnBsb3RfcGVybXV0ZV92YXJfaW1wKGJzX2xhc3NvX3Blcm0sIG1ldHJpYyA9IG1ldCkKYGBgCgojIyMjIENvZWZmaWNpZW50IHZzLiBQZXJtdXRhdGlvbiBpbXBvcnRhbmNlCk5vdywgd2UgY29tcGFyZSB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlcyBnZW5lcmF0ZWQgYnkgdGhlIHR3byBkaWZmZXJlbnQgYXBwcm9hY2hlcy4gIFRoZSB0cmFkaXRpb25hbCBtZXRob2Qgb2YgZXZhbHVhdGluZyBmZWF0dXJlIGltcG9ydGFuY2UgZm9yIHJlZ3Jlc3Npb24gbWV0aG9kcyBpcyB0aHJvdWdoIGFuYWx5c2lzIG9mIHRoZSBjb2VmZmljaWVudHMuCmBgYHtyIGZpZy53aWR0aCA9IDE2LCBmaWcuaGVpZ2h0ID0gMTR9CmNiaW5kKGJvb3RfbGFzc29fbWRpWzE6MjAsXSwgZHBseXI6OnNlbGVjdChic19sYXNzb19wZXJtWzE6MjAsXSwgLW1ldCkpCmBgYAoKIyMgQ29tcGFyaXNvbjogTW9kZWwgVHlwZSBNZWFuIFBlcmZvcm1hbmNlClRoZSBmb2xsb3dpbmcgdGFibGUgY29tcGFyZXMgdGhlIG1lYW4gcGVyZm9ybWFuY2Ugb2YgYm9vdHN0cmFwcGVkIHJhbmRvbSBmb3Jlc3RzIHRvIHRoZSBtZWFuIHBlcmZvcm1hbmNlIG9mIGJvb3RzdHJhcHBlZCBMQVNTTyBtZXRob2RzLgpgYGB7ciBjb21wYXJlIGJzIG1lYW4gcGVyZm9ybWFuY2V9CmJzX2NvbXBfcGVyZnMgPC0gcmJpbmQobWVhbl9ic19yZl9wZXJmLCBtZWFuX2JzX2xhc3NvX3BlcmYpIApic19jb21wX3BlcmZzCmBgYAoKIyMgQ29tcGFyaXNvbjogTW9kZWwgVHlwZSBGZWF0dXJlIEltcG9ydGFuY2UKSGVyZSwgd2UgbG9vayBhdCB0aGUgYWdncmVnYXRlZCByZXN1bHRzIG9mIHRoZSBib290c3RyYXBwZWQgcHJlZGljdG9ycyBhbmQgY29tcGFyZSB0aGUgbW9kZWxzIGdlbmVyYXRlZCB0byBlYWNoIG90aGVyLgpgYGB7ciBjb21wcmUgYnMgZmVhdHVyZSBpbXBvcnRhbmNlfQpqb2luZWRfcmVzdWx0cyA8LSBic19yZl9wZXJtICU+JQogIGRwbHlyOjpzZWxlY3QoLW1ldCkgJT4lCiAgZnVsbF9qb2luKGRwbHlyOjpzZWxlY3QoYnNfbGFzc29fcGVybSwgLW1ldCksIGJ5PWMoInByZWRpY3RvciIsICJhdHRfbmFtZSIpLCBzdWZmaXg9YygnLnJmJywgJy5sYXNzbycpKSAlPiUKICBtdXRhdGUobWVhbl9yYW5rID0gKG92ZXJhbGxfcmFuay5yZitvdmVyYWxsX3JhbmsubGFzc28pLzIpICU+JQogIGFycmFuZ2UobWVhbl9yYW5rKQoKaGVhZChqb2luZWRfcmVzdWx0cywgMjApCmBgYAoKVGhlIGZvbGxvd2luZyB2aXN1YWxpemF0aW9uIHByb3ZpZGVzIHRoZSBpbnR1aXRpb24gYWJvdXQgdGhlIGRpZmZlcmVuY2VzIGluIHRoZSByYW5raW5ncyBiZXR3ZWVuIG1vZGVsIHR5cGVzLiAgVGhleSdyZSBvcmRlcmVkIGJ5IHRoZSBvdmVyYWxsIG1lYW4gaW1wb3J0YW5jZSwgYW5kIGZvciBhIGdpdmVuIHZhcmlhYmxlLCB0aGUgZGlmZmVyZW5jZXMgaW4gcmFuayBhcmUgc2hvd24uCmBgYHtyIGJzIGZlYXR1cmUgaW1wb3J0YW5jZSBwbG90bHksIGZpZy53aWR0aCA9IDE2LCBmaWcuaGVpZ2h0ID0gMTR9CiMgQ29tcGFyaXNvbiBvZiB0b3BfbiBmZWF0dXJlcwpqb2luZWRfcmVzdWx0cyAlPiUKICBjb21wYXJlX2ZlYXR1cmVfc2VsZWN0KGludGVyYWN0aXZlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgIHRvcF9uID0gMTAwLAogICAgICAgICAgICAgICAgICAgICAgICAgb3BhY2l0eSA9IDAuNTAsCiAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X3RpdGxlID0gIlBlcm11dGF0aW9uIEltcG9ydGFuY2Ugb2YgUHJlZGljdG9ycyBieSBNb2RlbCIpCmBgYAoKCiMgR2VuZXJhdGlvbiBvZiBmaW5hbCBtb2RlbCB7LnRhYnNldCAudGFic2V0LWZhZGUgLnRhYnNldC1waWxsc30KCiMjIFJGIG1vZGVsCkluIHRoaXMgc3RlcCwgd2UgYnVpbGQgdGhlIGZpbmFsIG1vZGVsIGZvciB0aGUgcmFuZG9tIGZvcmVzdC4gIFdlIHVzZSBzbGlnaHRseSBtb3JlIHZhbHVlcyBpbiBvcmRlciB0byBjb21lIHVwIHdpdGggdGhlIGJlc3QgbW9kZWwsIGtlZXBpbmcgaW4gbWluZCB0aGUgbnVtYmVyIG9mIGNvbWJpbmF0aW9ucyB0aGF0IGFyZSByZXF1aXJlZCB0byBydW4gdG8gZXZhbHVhdGUgdGhlIGdyaWQuCgpgYGB7ciBzYXZlL2xvYWQgZmluYWwgcmYgbW9kZWx9CgpmaW5hbF9yZl9wZXJmID0gTlVMTAogIAojIHJlYWQgZmlsZSBhcnJheQpmaW5hbF9yZl9wZXJmcyA9IGxvYWRfZnJvbV9jc3YoZmluYWxfcmZfcGVyZiwgcmVzdWx0c19kaXJlY3RvcnksIG5vX3Rhc2tzKQoKIyBnZXQgdGhlIGluZGV4IG9mIHRoZSBiZXN0IHBlcmZvcm1hbmNlIGFuZCBiZXN0IHBlcmZvcm1hbmNlCmZpbmFsX3JmbW9kZWxfaW5kIDwtIHdoaWNoLm1heChkcGx5cjo6c2VsZWN0KGZpbmFsX3JmX3BlcmZzLCBtZXQpICU+JSBwdWxsKG1ldCkpCmZpbmFsX3JmX3BlcmYgPC0gZmluYWxfcmZfcGVyZnMgJT4lIHNsaWNlKGZpbmFsX3JmbW9kZWxfaW5kKQoKIyBsb2FkIHRoZSBtb2RlbCBvZiBpbnRlcmVzdApmaW5hbF9tb2RlbF9yZiA8LSBsb2FkX2Jlc3RfbW9kZWwoJ2ZpbmFsX3JmX21vZGVsJywgcmVzdWx0c19kaXJlY3RvcnksIG5vX3Rhc2tzW1tmaW5hbF9yZm1vZGVsX2luZF1dKQoKYGBgCgojIyMgUGVyZm9ybWFuY2UKVGhlIGZpbmFsIHJhbmRvbSBmb3Jlc3QgcGVyZm9ybWFuY2UgbWV0cmljcyBhcmUgc2hvd24gYmVsb3c6CmBgYHtyIHJmIGZpbmFsIG1vZGVsIHBlcmZvcm1hbmNlfQojIHNob3cgbW9kZWwgZmluYWwgcGVyZm9ybWFuY2UKcHJpbnQoZmluYWxfcmZfcGVyZikKYGBgCgojIyMgRmVhdHVyZXM6IHBlcm11dGF0aW9uIGltcG9ydGFuY2UKCmBgYHtyIGNvbXB1dGUvc2F2ZSBvciBsb2FkIGZpbmFsIHJmIG1vZGVsIHBlcm11dGF0aW9ufQoKZmluYWxfcmZfcGVybV9wbHQgPSBOVUxMCiAgCiMgcmVhZCBiZXN0IHBlcm0gcGx0CmZpbmFsX3JmX3Blcm1fcGx0ID0gbG9hZF9mcm9tX2NzdihmaW5hbF9yZl9wZXJtX3BsdCwgcmVzdWx0c19kaXJlY3RvcnksIGJlc3RfaW5kPW5vX3Rhc2tzW1tmaW5hbF9yZm1vZGVsX2luZF1dKQoKYGBgCgoKYGBge3IgZ2V0IG1ldHJpY3MgZm9yIGZpbmFsIHJmfQpmaW5hbF9yZl9wZXJtIDwtIGZpbmFsX3JmX3Blcm1fcGx0ICU+JQogIGdldF9wZXJtdXRlX3BsYWNlbWVudChtZXRyaWNfb2k9bWV0KSAlPiUKICBhZGRfYXR0cmlidXRlX25hbWVzKCdwcmVkaWN0b3InLCBmdWxsX2RhdGFzZXQpICU+JQogIGRwbHlyOjpzZWxlY3QocHJlZGljdG9yLCBldmVyeXRoaW5nKCkpCgpoZWFkKGZpbmFsX3JmX3Blcm0sIDIwKQpgYGAKCmBgYHtyIHBsb3QgcmYgZmluYWwgcGVybXV0YXRpb24sIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTJ9CnBsb3RfcGVybXV0ZV92YXJfaW1wKGZpbmFsX3JmX3Blcm0sIG1ldHJpYyA9IG1ldCkKYGBgCiMjIyBDb21wYXJpc29uIHdpdGggYm9vdHN0cmFwIHJlc3VsdHMKVGhpcyBzZWN0aW9uIGludmVzdGlnYXRlcyB0aGUgZGlmZmVyZW5jZXMgaW4gdGhlIGJvb3RzdHJhcCByZXN1bHRzIHZzIHRoZSBmZWF0dXJlcyBnZW5lcmF0ZWQgZnJvbSB0aGUgcmFuZG9tIGZvcmVzdCBmaW5hbCBtb2RlbC4gIFRoZSBmb2xsb3dpbmcgdGFibGUgc2hvd3MgdGhlIG92ZXJhbGwgZGlmZmVyZW5jZXMgaW4gcmFuay4KCmBgYHtyIGNvbXBhcmUgcmYgZmluYWwgd2l0aCBib290c3RyYXB9CnJmX2pvaW5lZF9yZXN1bHRzIDwtIGZpbmFsX3JmX3Blcm0gJT4lCiAgZHBseXI6OnNlbGVjdCgtbWV0KSAlPiUKICBmdWxsX2pvaW4oZHBseXI6OnNlbGVjdChic19yZl9wZXJtLCAtbWV0KSwgYnk9YygicHJlZGljdG9yIiwgImF0dF9uYW1lIiksIHN1ZmZpeD1jKCcuZmluYWwnLCAnLmJvb3RzdHJhcCcpKSAlPiUKICBtdXRhdGUobWVhbl9yYW5rID0gKG92ZXJhbGxfcmFuay5maW5hbCArIG92ZXJhbGxfcmFuay5ib290c3RyYXApLzIpICU+JQogIGFycmFuZ2UobWVhbl9yYW5rKQoKaGVhZChyZl9qb2luZWRfcmVzdWx0cywgMjApCmBgYAoKVGhlIGZvbGxvd2luZyBwbG90IHByb3ZpZGVzIHZpc3VhbGl6YXRpb25zIGZvciB0aGUgZGlmZmVyZW5jZSBpbiB0aGUgZmluYWwgbW9kZWwgcmFua2luZ3MgdnMgdGhlIGJvb3RzdHJhcC4KCmBgYHtyIHBsb3QgZmluYWwgcmYgZmVhdHVyZXMgcGxvdGx5LCBmaWcuaGVpZ2h0PTE0LCBmaWcud2lkdGg9MTZ9CiMgQ29tcGFyaXNvbiBvZiB0b3BfbiBmZWF0dXJlcwpyZl9qb2luZWRfcmVzdWx0cyAlPiUKICBjb21wYXJlX2ZlYXR1cmVfc2VsZWN0KHNlbF9jb2xzID0gYygib3ZlcmFsbF9yYW5rLmZpbmFsIiwgIm92ZXJhbGxfcmFuay5ib290c3RyYXAiKSwKICAgIGludGVyYWN0aXZlID0gVFJVRSwKICAgIHRvcF9uID0gMTAwLAogICAgb3BhY2l0eSA9IDAuNTAsCiAgICBwbG90X3RpdGxlID0gIlBlcm11dGF0aW9uIEltcG9ydGFuY2Ugb2YgUHJlZGljdG9yczogRmluYWwgdnMuIEJvb3RzdHJhcCIpCmBgYAoKIyMgTEFTU08gbW9kZWwKTm93LCB3ZSBjcmVhdGUgdGhlIGZpbmFsIG1vZGVsIGZvciBMQVNTTy4gIFRoZXJlIGlzIG5vIHN1YnN0YW50aWFsIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGlzIG1ldGhvZCBhbmQgdGhlIGJvb3RzdHJhcCBtZXRob2RzLCBvdGhlciB0aGFuIHRoZSBkYXRhIHVwb24gd2hpY2ggdGhlIG1vZGVsIGlzIGJlaW5nIGJ1aWx0LgoKYGBge3Igc2F2ZS9sb2FkIGZpbmFsIGxhc3NvIG1vZGVsfQoKZmluYWxfbGFzc29fcGVyZiA9IE5VTEwKICAKIyByZWFkIGZpbGUgYXJyYXkKZmluYWxfbGFzc29fcGVyZnMgPSBsb2FkX2Zyb21fY3N2KGZpbmFsX2xhc3NvX3BlcmYsIHJlc3VsdHNfZGlyZWN0b3J5LCBub190YXNrcykKCiMgZ2V0IHRoZSBpbmRleCBvZiB0aGUgYmVzdCBwZXJmb3JtYW5jZSBhbmQgYmVzdCBwZXJmb3JtYW5jZQpmaW5hbF9sYXNzb21vZGVsX2luZCA8LSB3aGljaC5tYXgoZHBseXI6OnNlbGVjdChmaW5hbF9sYXNzb19wZXJmcywgbWV0KSAlPiUgcHVsbChtZXQpKQpmaW5hbF9sYXNzb19wZXJmIDwtIGZpbmFsX2xhc3NvX3BlcmZzICU+JSBzbGljZShmaW5hbF9sYXNzb21vZGVsX2luZCkKCiMgbG9hZCB0aGUgbW9kZWwgb2YgaW50ZXJlc3QKZmluYWxfbW9kZWxfbGFzc28gPC0gbG9hZF9iZXN0X21vZGVsKCdmaW5hbF9sYXNzb19tb2RlbCcsIHJlc3VsdHNfZGlyZWN0b3J5LCBub190YXNrc1tbZmluYWxfbGFzc29tb2RlbF9pbmRdXSApCgpgYGAKClRoZSBmaW5hbCBMQVNTTyBwZXJmb3JtYW5jZSBtZXRyaWNzIGFyZSBzaG93biBiZWxvdzoKYGBge3IgbGFzc28gZmluYWwgbW9kZWwgcGVyZm9ybWFuY2V9CiMgc2hvdyBtb2RlbCBmaW5hbCBwZXJmb3JtYW5jZQpwcmludChmaW5hbF9sYXNzb19wZXJmKQpgYGAKCiMjIyBGZWF0dXJlczogcGVybXV0YXRpb24gaW1wb3J0YW5jZQoKYGBge3IgY29tcHV0ZS9zYXZlIG9yIGxvYWQgZmluYWwgbGFzc28gbW9kZWwgcGVybXV0YXRpb259CgpmaW5hbF9sYXNzb19wZXJtX3BsdCA9IE5VTEwKCiNsb2FkIGJlc3QgaW5kZXggcGVybXV0YXRpb24gZnJvbSBmaWxlICAKZmluYWxfbGFzc29fcGVybV9wbHQgPSBsb2FkX2Zyb21fY3N2KGZpbmFsX2xhc3NvX3Blcm1fcGx0LCByZXN1bHRzX2RpcmVjdG9yeSwgYmVzdF9pbmQ9bm9fdGFza3NbW2ZpbmFsX2xhc3NvbW9kZWxfaW5kXV0pCgoKYGBgCgpgYGB7ciBnZXQgcGVybXV0ZSBmZWF0dXJlcyBmb3IgZmluYWwgbGFzc299CmZpbmFsX2xhc3NvX3Blcm0gPC0gZmluYWxfbGFzc29fcGVybV9wbHQgJT4lCiAgZ2V0X3Blcm11dGVfcGxhY2VtZW50KG1ldHJpY19vaT1tZXQpICU+JQogIGFkZF9hdHRyaWJ1dGVfbmFtZXMoJ3ByZWRpY3RvcicsIGZ1bGxfZGF0YXNldCkgJT4lCiAgZHBseXI6OnNlbGVjdChwcmVkaWN0b3IsIGV2ZXJ5dGhpbmcoKSkKCmhlYWQoZmluYWxfbGFzc29fcGVybSwgMjApCmBgYAoKYGBge3IgcGxvdCBsYXNzbyBmaW5hbCBwZXJtdXRhdGlvbiwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMn0KcGxvdF9wZXJtdXRlX3Zhcl9pbXAoZmluYWxfbGFzc29fcGVybSwgbWV0cmljID0gbWV0KQpgYGAKIyMjIENvbXBhcmlzb24gd2l0aCBib290c3RyYXAgcmVzdWx0cwpUaGlzIHNlY3Rpb24gaW52ZXN0aWdhdGVzIHRoZSBkaWZmZXJlbmNlcyBpbiB0aGUgYm9vdHN0cmFwIHJlc3VsdHMgdnMgdGhlIGZlYXR1cmVzIGdlbmVyYXRlZCBmcm9tIHRoZSBMQVNTTyBmaW5hbCBtb2RlbC4gIFRoZSBmb2xsb3dpbmcgdGFibGUgc2hvd3MgdGhlIG92ZXJhbGwgZGlmZmVyZW5jZXMgaW4gcmFuay4KCmBgYHtyIGNvbXBhcmUgbGFzc28gZmluYWwgd2l0aCBib290c3RyYXB9Cmxhc3NvX2pvaW5lZF9yZXN1bHRzIDwtIGZpbmFsX2xhc3NvX3Blcm0gJT4lCiAgZHBseXI6OnNlbGVjdCgtbWV0KSAlPiUKICBmdWxsX2pvaW4oZHBseXI6OnNlbGVjdChic19sYXNzb19wZXJtLCAtbWV0KSwgYnk9YygicHJlZGljdG9yIiwgImF0dF9uYW1lIiksIHN1ZmZpeD1jKCcuZmluYWwnLCAnLmJvb3RzdHJhcCcpKSAlPiUKICBtdXRhdGUobWVhbl9yYW5rID0gKG92ZXJhbGxfcmFuay5maW5hbCArIG92ZXJhbGxfcmFuay5ib290c3RyYXApLzIpICU+JQogIGFycmFuZ2UobWVhbl9yYW5rKQoKaGVhZChsYXNzb19qb2luZWRfcmVzdWx0cywgMjApCmBgYAoKVGhlIGZvbGxvd2luZyBwbG90IHByb3ZpZGVzIHZpc3VhbGl6YXRpb25zIGZvciB0aGUgZGlmZmVyZW5jZSBpbiB0aGUgZmluYWwgbW9kZWwgcmFua2luZ3MgdnMgdGhlIGJvb3RzdHJhcC4KCmBgYHtyIHBsb3QgbGFzc28gZmVhdHVyZSBjb21wYXJpc29uLCBmaWcud2lkdGggPSAxNiwgZmlnLmhlaWdodCA9IDE0fQojIENvbXBhcmlzb24gb2YgdG9wX24gZmVhdHVyZXMKbGFzc29fam9pbmVkX3Jlc3VsdHMgJT4lCiAgY29tcGFyZV9mZWF0dXJlX3NlbGVjdChzZWxfY29scyA9IGMoIm92ZXJhbGxfcmFuay5maW5hbCIsICJvdmVyYWxsX3JhbmsuYm9vdHN0cmFwIiksCiAgICBpbnRlcmFjdGl2ZSA9IFRSVUUsCiAgICB0b3BfbiA9IDEwMCwKICAgIG9wYWNpdHkgPSAwLjUwLAogICAgcGxvdF90aXRsZSA9ICJQZXJtdXRhdGlvbiBJbXBvcnRhbmNlIG9mIFByZWRpY3RvcnM6IEZpbmFsIHZzLiBCb290c3RyYXAiKQpgYGAKCiMjIENvbXBhcmlzb246IEZpbmFsIG1vZGVsIGZlYXR1cmVzCkhlcmUsIHdlIGNvbXBhcmUgdGhlIGZlYXR1cmVzIGdlbmVyYXRlZCBieSB0aGUgcGVybXV0YXRpb24gaW1wb3J0YW5jZSBiZXR3ZWVuIHRoZSB0d28gZmluYWwgbW9kZWxzLgoKYGBge3IgY29tcGFyZSBsYXNzbyB2cyByYW5kb20gZm9yZXN0IGZlYXR1cmVzIGZpbmFsfQpyZl9sYXNzb19maW5hbF9qb2luZWRfcmVzdWx0cyA8LSBmaW5hbF9yZl9wZXJtICU+JQogIGRwbHlyOjpzZWxlY3QoLW1ldCkgJT4lCiAgZnVsbF9qb2luKGRwbHlyOjpzZWxlY3QoZmluYWxfbGFzc29fcGVybSwgLW1ldCksIGJ5PWMoInByZWRpY3RvciIsICJhdHRfbmFtZSIpLCBzdWZmaXg9YygnLnJmJywgJy5sYXNzbycpKSAlPiUKICBtdXRhdGUobWVhbl9yYW5rID0gKG92ZXJhbGxfcmFuay5yZitvdmVyYWxsX3JhbmsubGFzc28pLzIpICU+JQogIGFycmFuZ2UobWVhbl9yYW5rKQoKaGVhZChyZl9sYXNzb19maW5hbF9qb2luZWRfcmVzdWx0cywgMjApCmBgYAoKVGhlIGZvbGxvd2luZyB2aXN1YWxpemF0aW9uIHByb3ZpZGVzIHRoZSBpbnR1aXRpb24gYWJvdXQgdGhlIGRpZmZlcmVuY2VzIGluIHRoZSByYW5raW5ncyBiZXR3ZWVuIHRoZSBmaW5hbCBtb2RlbCB0eXBlcy4gIFRoZXkncmUgb3JkZXJlZCBieSB0aGUgb3ZlcmFsbCBtZWFuIGltcG9ydGFuY2UsIGFuZCBmb3IgYSBnaXZlbiB2YXJpYWJsZSwgdGhlIGRpZmZlcmVuY2VzIGluIHJhbmsgYXJlIHNob3duLgoKYGBge3IgcGxvdGx5IGxhc3NvIHYgcmFuZG9tIGZvcmVzdCBmZWF0dXJlc30KIyBDb21wYXJpc29uIG9mIHRvcF9uIGZlYXR1cmVzCnJmX2xhc3NvX2ZpbmFsX2pvaW5lZF9yZXN1bHRzICU+JQogIGNvbXBhcmVfZmVhdHVyZV9zZWxlY3Qoc2VsX2NvbHMgPSBjKCJvdmVyYWxsX3JhbmsucmYiLCAib3ZlcmFsbF9yYW5rLmxhc3NvIiksCiAgICBpbnRlcmFjdGl2ZSA9IFRSVUUsCiAgICB0b3BfbiA9IDEwMCwKICAgIG9wYWNpdHkgPSAwLjUwLAogICAgcGxvdF90aXRsZSA9ICJQZXJtdXRhdGlvbiBJbXBvcnRhbmNlIG9mIFByZWRpY3RvcnM6IFJhbmRvbSBGb3Jlc3QgdnMgTGFzc28iKQpgYGAKCiMjIENvbXBhcmlzb246IEZpbmFsIG1vZGVsIHBlcmZvcm1hbmNlCldpdGggdGhlIGZpbmFsIG1vZGVscyBnZW5lcmF0ZWQsIHdlJ3JlIG5vdyBhYmxlIHRvIGNvbXBhcmUgdGhlaXIgcGVyZm9ybWFuY2UgbWV0cmljcy4KYGBge3IgZmluYWwgbW9kZWwgY29tcGFyaXNvbiwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSAxMn0KIyBDb21wYXJpc29uIG9mIHBlcmZvcm1hbmNlIG1ldHJpY3MKdmFsaWRfcGVyZiA8LSBnZXRfbWV0cmljX3NldF9mcm9tX3BlcmZzKHBlcmZfbGlzdCA9IGxpc3QoZmluYWxfcmZfcGVyZiwgZmluYWxfbGFzc29fcGVyZikpICU+JQogIG11dGF0ZShtb2RlbCA9IGMoJ3JmJywgJ2xhc3NvJykpCgp0ZXN0aW5nX3BlcmYgPC0gZ2V0X21ldHJpY19zZXRfZnJvbV9tb2RlbHModGVzdGluZ19kZiwgbGlzdChmaW5hbF9tb2RlbF9yZiwgZmluYWxfbW9kZWxfbGFzc28pLCBvdXQ9b3V0Y29tZSkgJT4lCiAgbXV0YXRlKG1vZGVsID0gYygncmYnLCAnbGFzc28nKSkKYGBgCgoqKlZhbGlkYXRpb24gYW5kIHNlbGVjdGlvbi4qKgpUaGUgZm9sbG93aW5nIHRhYmxlIHNob3dzIHRoZSBjb21wYXJpc29uIGJldHdlZW4gbW9kZWxzIGluIHRlcm1zIG9mIHRoZSB2YWxpZGF0aW9uIHNldC4gIFdlIGNhbiBzZWxlY3Qgb3VyIGZpbmFsIG1vZGVsIGJhc2VkIG9uIHRoZSBiZXN0IHBlcmZvcm1pbmcgbW9kZWwgYWNjb3JkaW5nIHRvIHRoZSBtZXRyaWMuCmBgYHtyfQpwcmludCh2YWxpZF9wZXJmKQpgYGAKCioqVGVzdGluZyBwZXJmb3JtYW5jZS4qKgpUaGUgZm9sbG93aW5nIHNob3dzIHRoZSBwZXJmb3JtYW5jZSBvZiBib3RoIHRoZSBtb2RlbHMgb24gdGhlIHRlc3Qgc2V0LiAgTm90ZSB0aGF0IGFsdGhvdWdoIHdlIGRvbid0IHVzZSB0aGlzIHRlc3Qgc2V0IHRvIGV2YWx1YXRlIHRoZSBmaW5hbCBtb2RlbHMsIHdlIGNhbiBzdGlsbCBzZWUgaG93IG91ciBzZWxlY3RlZCBtZXRob2Qgd291bGQgaGF2ZSBwZXJmb3JtZWQuCmBgYHtyfQpwcmludCh0ZXN0aW5nX3BlcmYpCmBgYAoKVGhlIGZvbGxvd2luZyBwbG90cyBzaG93IGEgY29tcGFyaXNvbiBiZXR3ZWVuIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWxzIG9uIHRoZSB2YWxpZGF0aW9uIGFuZCB0ZXN0IHNldHMuICBBZ2Fpbiwgd2UgZG9uJ3QgY2hvb3NlIHRoZSBtb2RlbCBiYXNlZCBvbiB0aGUgdGVzdCBzZXQsIGJ1dCBjdXJpb3NpdHkgZGljdGF0ZXMgdGhhdCB3ZSB2aWV3IHRoaXMgcGVyZm9ybWFuY2UuCgpgYGB7ciBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CiMgU2hvdyBwbG90cyBzaWRlIGJ5IHNpZGUKbWV0cmljc19vZl9pbnRlcmVzdCA9IGMoJ21vZGVsJywgJ2FjY3VyYWN5JywgJ2JhbF9hY2N1cmFjeScsICdtcGNlJywgJ3NlbnMnLCAnc3BlYycsICdwcHYnLCAnbnB2JywgJ3ByX2F1YycsICdyb2NfYXVjJykKdmFsaWRfcGx0IDwtIHBsb3RfbWV0cmljX3NldChkcGx5cjo6c2VsZWN0KHZhbGlkX3BlcmYsIGFsbF9vZihtZXRyaWNzX29mX2ludGVyZXN0KSksIHBsb3RfdGl0bGUgPSAiTW9kZWwgY29tcGFyaXNvbiBmb3IgdmFsaWRhdGlvbiBzZXQiKQp0ZXN0X3BsdCA8LSBwbG90X21ldHJpY19zZXQoZHBseXI6OnNlbGVjdCh0ZXN0aW5nX3BlcmYsIGFsbF9vZihtZXRyaWNzX29mX2ludGVyZXN0KSksIHBsb3RfdGl0bGUgPSAiTW9kZWwgY29tcGFyaXNvbiBmb3IgdGVzdGluZyBzZXQiKQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShncmlkRXh0cmE6OmFycmFuZ2VHcm9iKHZhbGlkX3BsdCwgdGVzdF9wbHQsIG5jb2w9MiwgbnJvdz0xKSkKYGBgCgo=